﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Runtime.Remoting.Messaging;
using Castle.DynamicProxy;
using NMock2.Syntax;

namespace NMock2.Internal
{
	internal class ClassExpectationBuilder<T> : InterfaceExpectationBuilder<T>
		where T : class
	{
		public ClassExpectationBuilder(string description, Matcher requiredCountMatcher, Matcher acceptedCountMatcher, object mockObject)
			: base(description, requiredCountMatcher, acceptedCountMatcher, mockObject)
		{
		}

		#region SetProperty

		public override IAutoValueSyntax<TProperty> SetProperty<TProperty>(Func<T, TProperty> function)
		{
			bool isIndexer = false;

			using (ClassMessageGrabber<T> grabber = new ClassMessageGrabber<T>())
			//using (MessageGrabber grabber = new MessageGrabber(typeof(T)))
			{
				function(grabber.Target);

				IInvocation call = grabber.Calls[grabber.Calls.Count - 1];
				MethodInfo method = call.Method;

				if (!method.IsSpecialName)
					throw new InvalidOperationException("This method expects a property, not a method, or event.");

				string name = method.Name.Replace("set_", string.Empty).Replace("get_", string.Empty); //get is needed if is used like SetProperty(m=>m.prop)
				IValueSyntax valueSyntax = SetProperty(name);

				MethodBase mbase;
				Type[] parameterTypes = method.GetParameters().Select(p => p.ParameterType).ToArray();
				int i = FindIndexer(typeof(T), parameterTypes, out mbase);

				//PropertyInfo propertyInfo = typeof (T).GetProperty(name, new[] {method.GetParameters()[0].ParameterType});

				//ParameterInfo[] parameters = method.GetParameters();

				//check for args
				if (i == 1)
				{
					//properties have args when used like SetProperty(m=>m.prop[1] = "d") or SetProperty(m=>m.prop[1]) or SetProperty(m=>m.prop[1,3])
					//grab all but the last one.  If the user wants to grab both then use SetPropertyTo.
					base.With(call.Arguments[0]);
					isIndexer = true;
				}

				return new PropertyValueBuilder<TProperty>(this, isIndexer);
			}
		}

		public override ICommentSyntax SetPropertyTo(Action<T> action)
		{
			using (ClassMessageGrabber<T> grabber = new ClassMessageGrabber<T>())
			//using (MessageGrabber grabber = new MessageGrabber(typeof(T)))
			{
				action(grabber.Target);

				IInvocation call = grabber.Calls[grabber.Calls.Count - 1];
				MethodInfo method = call.Method;
				IValueSyntax valueSyntax = SetProperty(method.Name.Replace("set_", string.Empty));

				//check for args
				if (call.Arguments.Length > 1)
				{
					//properties have args when used like SetPropertyTo(m=>m.prop[1] = "d")
					base.With(call.Arguments);
				}
				else
				{
					valueSyntax.To(call.Arguments[0]);
				}


				return this;
			}
		}

		#endregion

		/// <summary>
		/// Ensures the matching method exists on mock.
		/// </summary>
		/// <param name="methodMatcher">The method matcher.</param>
		/// <param name="methodDescription">The method description.</param>
		protected override void EnsureMatchingMethodExistsOnMock(Matcher methodMatcher, string methodDescription)
		{
			try
			{
				base.EnsureMatchingMethodExistsOnMock(methodMatcher, methodDescription);
			}
			catch (Exception err)
			{
				if(IsStub && err.Message.Contains("but it is not virtual"))
					return;
				throw;
			}
		}
	}
}
